package gapidt.plugin.wizards;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.log4j.Logger;
import org.apache.ws.java2wsdl.Java2WSDLBuilder;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.operation.*;

import gapidt.plugin.GapidtActivator;
import gapidt.plugin.UserClassLoader;
import gapidt.plugin.WorkbenchUtils;
import gapidt.wsdl.Java2WSDL;
import gapidt.wsdl.Java2WSDL11;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.*;
import org.ow2.easywsdl.tooling.java2wsdl.JavaToEasyWSDL;
import org.ow2.easywsdl.tooling.java2wsdl.WSDLGenerationContext;
import org.ow2.easywsdl.wsdl.WSDLFactory;
import org.ow2.easywsdl.wsdl.api.Description;
import org.ow2.easywsdl.wsdl.api.Types;
import org.ow2.easywsdl.wsdl.api.WSDLException;
import org.w3c.dom.Document;

import com.predic8.schema.Schema;
import com.predic8.wsdl.Definitions;
import com.predic8.wsdl.Import;

/**
 * This is a sample new wizard. Its role is to create a new file 
 * resource in the provided container. If the container resource
 * (a folder or a project) is selected in the workspace 
 * when the wizard is opened, it will accept it as the target
 * container. The wizard creates one file with the extension
 * "mpe". If a sample multi-page editor (also available
 * as a template) is registered for the same extension, it will
 * be able to open it.
 */

public class WsdlGeneratorWizard extends Wizard implements INewWizard {
	
	private static Logger logger = Logger.getLogger(WsdlGeneratorWizard.class);
	
	public final static int WSDL_GENERATOR_GAPIDT = 0;
	public final static int WSDL_GENERATOR_AXIS2 = 1;
	public final static int WSDL_GENERATOR_OW2 = 2;
	
	private WsdlGeneratorPage1 page1;
	private WsdlGeneratorPage2 page2;
	private IType[] types;
	private IJavaProject javaProject;

	/**
	 * Constructor for SampleNewWizard.
	 */
	public WsdlGeneratorWizard(IType[] types, IJavaProject javaProject) {
		super();
		this.types = types;
		this.javaProject = javaProject;
		setNeedsProgressMonitor(true);
	}
	
	/**
	 * Adding the page to the wizard.
	 */

	public void addPages() {
		page1 = new WsdlGeneratorPage1(types, javaProject);
		page2 = new WsdlGeneratorPage2(types, javaProject);
		addPage(page1);
		addPage(page2);
	}

	/**
	 * This method is called when 'Finish' button is pressed in
	 * the wizard. We will create an operation and run it
	 * using wizard as execution context.
	 */
	public boolean performFinish() {
		final int wsdlGenerationMethod = page1.getWsdlGenerationMethod();
		final String wsdlFileName = page1.getWsdlFileName();
		final String wsdlFileContainerName = page1.getWsdlFileContainerName();
		final String wsdlTargetNamespace = page1.getWsdlTargetNamespace();
		final boolean enclosedSchema = page2.getEnclosedSchema();
		final String schemaFileName = page2.getSchemaFileName();
		final String schemaFileContainerName = page2.getSchemaFileContainerName();
		final String schemaTargetNamespace = page2.getSchemaTargetNamespace();
		final String schemaPublicUrl = page2.getSchemaPublicUrl();
		final IType[] selectedTypes = page1.getSelectedTypes();
		
		IRunnableWithProgress op = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException {
				try {
					doFinish(wsdlGenerationMethod,
							selectedTypes,
							wsdlFileName, 
							wsdlFileContainerName, 
							wsdlTargetNamespace, 
							enclosedSchema, 
							schemaFileName,
							schemaFileContainerName, 
							schemaTargetNamespace, 
							schemaPublicUrl, 
							monitor);
				} catch (Exception e) {
					throw new InvocationTargetException(e);
				} finally {
					monitor.done();
				}
			}
		};
		try {
			getContainer().run(true, false, op);
		} catch (InterruptedException e) {
			return false;
		} catch (InvocationTargetException e) {
			Throwable realException = e.getTargetException();
			MessageDialog.openError(getShell(), "Error", realException.getMessage());
			return false;
		}
		return true;
	}
	
	/**
	 * The worker method. It will find the container, create the
	 * file if missing or just replace its contents, and open
	 * the editor on the newly created file.
	 */

	private void doFinish(int wsdlGenerationMethod,
			IType[] selectedTypes,
			String wsdlFileName, 
			String wsdlContainerName, 
			String wsdlTargetNamespace, 
			boolean enclosedSchema, 
			String schemaFileName, 
			String schemaContainerName, 
			String schemaTargetNamespace, 
			String schemaPublicUrl, 
			IProgressMonitor monitor) throws Exception{
		
		// create a sample file
		monitor.beginTask("Creating " + wsdlFileName, 2);
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IResource resource = root.findMember(new Path(wsdlContainerName));
		if (!resource.exists() || !(resource instanceof IContainer)) {
			throwCoreException("Container \"" + wsdlContainerName + "\" does not exist.");
		}
		IContainer container = (IContainer) resource;
		
		final IFile wsdlFile = container.getFile(new Path(wsdlFileName));
		final IFile schemaFile = container.getFile(new Path(schemaFileName));
		
		String wsdlFileContents = "";
		String schemaFileContents = "";
		UserClassLoader userClassLoader = null;
		
		switch(wsdlGenerationMethod){
			case WSDL_GENERATOR_AXIS2:
				//Generate WSDL using Axis2 Java2WSDL tool
				userClassLoader = new UserClassLoader(javaProject);
				
				try {
					ByteArrayOutputStream boa = new ByteArrayOutputStream();
					Class<?> serviceClass = userClassLoader.findClass(selectedTypes[0].getFullyQualifiedName());
					if(serviceClass != null){
						
						Java2WSDLBuilder builder = new Java2WSDLBuilder(boa, serviceClass.getCanonicalName(), userClassLoader);
						builder.setTargetNamespace(wsdlTargetNamespace);
						builder.setSchemaTargetNamespace(schemaTargetNamespace);
					
						builder.generateWSDL();
						wsdlFileContents = boa.toString("UTF-8");
					}else{
						WorkbenchUtils.showErrorMessage("Error", "An error occurred while compiling the service class '"+selectedTypes[0].getFullyQualifiedName()+"'");
						return;
					}
				} catch (Exception e) {
					logger.error("Exception while generating WSDL using Axis2", e);
					e.printStackTrace();
					WorkbenchUtils.showErrorMessage("Error", "An error occurred while compiling the service class");
				}
			break;
			case WSDL_GENERATOR_OW2:
				//Generate WSDL using Ow2 EasyWSDL tool
				
				try {
					userClassLoader = new UserClassLoader(javaProject);
				
					List<String> polymorphClasses = new ArrayList<String>();
					
					List<Class<?>> classes = new ArrayList<Class<?>>();
					
					for(IType type : selectedTypes){
						
						Class<?> serviceClass = userClassLoader.findClass(type.getFullyQualifiedName());
						if(serviceClass != null){
							classes.add(serviceClass);
						}else{
							WorkbenchUtils.showErrorMessage("Error", "An error occurred while compiling the service class '"+type.getFullyQualifiedName()+"'");
							return;
						}
					}
					
					JavaToEasyWSDL javaToWsdl = new JavaToEasyWSDL(true);

					
					WSDLGenerationContext genCtx = javaToWsdl.generateWSDL(classes, false, polymorphClasses);
					Description wsdl  = genCtx.getDescription();
					org.ow2.easywsdl.schema.api.Schema schema  = genCtx.getSchema(wsdl.getTargetNamespace());
					Types types = wsdl.createTypes();
					types.addSchema(schema);
					wsdl.setTypes(types);
					
					// Write a WSDL 1.1 or 2.0 (depend of desc version)
					Document doc = WSDLFactory.newInstance().newWSDLWriter().getDocument(wsdl);
					
					TransformerFactory transFactory = TransformerFactory.newInstance();
					Transformer transformer = transFactory.newTransformer();
					StringWriter buffer = new StringWriter();
					transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
					transformer.setOutputProperty(OutputKeys.INDENT, "yes");
					transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
					
					transformer.transform(new DOMSource(doc), new StreamResult(buffer));
					wsdlFileContents = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+buffer.toString();
					
				} catch (Exception e) {
					logger.error("Exception while generating WSDL using OW2", e);
					e.printStackTrace();
					WorkbenchUtils.showErrorMessage("Error", "An error occurred while compiling the service class");
				}
				
			break;
			default:
				logger.debug("Creating Definitions and Schema elements");
				
				//WSDL and Schema initialization
				Definitions wsdl = new Definitions(wsdlTargetNamespace, "");
				Schema schema = new Schema(schemaTargetNamespace);
				
				if(!enclosedSchema){
					logger.debug("Adding schema import");
					addSchemaImport(wsdl, schema, schemaPublicUrl);
				}
				
				logger.debug("Creating Java2WSDL11 instance");

				Java2WSDL java2WSDL = new Java2WSDL11(wsdl, schema, enclosedSchema);
								
				for(IType type : selectedTypes){
					java2WSDL.addTypeService(type);
				}
				
				logger.debug("Getting generated wsdl and xsd content");
				
				wsdlFileContents = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+java2WSDL.getWsdl().getAsString().replaceAll("'", "\"");
				schemaFileContents = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+java2WSDL.getSchema().getAsString().replaceAll("'", "\"");
			break;
		}
		
		//Write WSDL file
		if(!wsdlFileContents.isEmpty()){
			logger.debug("Writing wsdl file");
			InputStream wsdlStream = new ByteArrayInputStream(wsdlFileContents.getBytes());
			if(wsdlFile.exists()){
				wsdlFile.setContents(wsdlStream, true, true, monitor);
			}else
				wsdlFile.create(wsdlStream, true, monitor);
			wsdlStream.close();
			logger.debug("Wsdl file written");
		}
		
		//Write XSD file
		if(!enclosedSchema && !schemaFileContents.isEmpty()){
			logger.debug("Writing xsd file");
			InputStream schemaStream = new ByteArrayInputStream(schemaFileContents.getBytes());
			if(schemaFile.exists())
				schemaFile.setContents(schemaStream, true, true, monitor);
			else
				schemaFile.create(schemaStream, true, monitor);
			schemaStream.close();
			logger.debug("Xsd file written");
		}
		
		monitor.worked(1);
	}
	
	private void addSchemaImport(Definitions wsdl, Schema schema, String schemaPublicUrl){
		
		boolean schemaAlreadyImported = false;
		//Check if schema is already imported
		for(Import i : wsdl.getImports())
			if(i.getNamespace().equalsIgnoreCase(schema.getTargetNamespace())){
				schemaAlreadyImported = true;
				break;
			}
		//Add schema import
		if(!schemaAlreadyImported){
			Import schemaImport = new Import();
			schemaImport.setNamespace(schema.getTargetNamespace());
			schemaImport.setLocation(schemaPublicUrl);
			wsdl.getImports().add(schemaImport);
		}
	}
	
	
	private void throwCoreException(String message) throws CoreException {
		IStatus status =
			new Status(IStatus.ERROR, GapidtActivator.PLUGIN_ID, IStatus.OK, message, null);
		throw new CoreException(status);
	}

	/**
	 * We will accept the selection in the workbench to see if
	 * we can initialize from it.
	 * @see IWorkbenchWizard#init(IWorkbench, IStructuredSelection)
	 */
	public void init(IWorkbench workbench, IStructuredSelection selection) {
		if (selection != null && selection.isEmpty() == false
				&& selection instanceof IStructuredSelection) {
			IStructuredSelection ssel = (IStructuredSelection) selection;
			Object obj = ssel.getFirstElement();
			if (obj instanceof IType)
				this.types = ArrayUtils.addAll(new IType[]{}, (IType) obj);
			else if (obj instanceof ICompilationUnit){
				try {
					ICompilationUnit cu = (ICompilationUnit) obj;
					if(cu.hasChildren())
						this.types = cu.getTypes();
				} catch (JavaModelException e) {
					e.printStackTrace();
				}
			}
		}
	}
}